
import * as THREE from 'three';
import { OBJLoader } from 'three/examples/jsm/Addons.js'

import Camera from '../core/Camera';
import { CameraType, DEFAULT_PERSPECTIVE_CAMERA_PARAMS } from '../core/Camera';
import Scene from '../core/Scene';
import type { MouseEvent } from 'react';

let __lastTimeStamp = 0;

const _SCENE_VIEW_COORD_GRID_OBJECT_3D = (() => {
    const _v3 = (x, y, z) => new THREE.Vector3(x, y, z);
    const inner_grid_line_endpoints = [
        // horz
        _v3(-10, 0, -9), _v3(10, 0, -9),
        _v3(-10, 0, -8), _v3(10, 0, -8),
        _v3(-10, 0, -7), _v3(10, 0, -7),
        _v3(-10, 0, -6), _v3(10, 0, -6),
        _v3(-10, 0, -5), _v3(10, 0, -5),
        _v3(-10, 0, -4), _v3(10, 0, -4),
        _v3(-10, 0, -3), _v3(10, 0, -3),
        _v3(-10, 0, -2), _v3(10, 0, -2),
        _v3(-10, 0, -1), _v3(10, 0, -1),
        _v3(-10, 0, 0), _v3(10, 0, 0),
        _v3(-10, 0, 1), _v3(10, 0, 1),
        _v3(-10, 0, 2), _v3(10, 0, 2),
        _v3(-10, 0, 3), _v3(10, 0, 3),
        _v3(-10, 0, 4), _v3(10, 0, 4),
        _v3(-10, 0, 5), _v3(10, 0, 5),
        _v3(-10, 0, 6), _v3(10, 0, 6),
        _v3(-10, 0, 7), _v3(10, 0, 7),
        _v3(-10, 0, 8), _v3(10, 0, 8),
        _v3(-10, 0, 9), _v3(10, 0, 9),

        // vert
        _v3(-9, 0, -10), _v3(-9, 0, 10),
        _v3(-8, 0, -10), _v3(-8, 0, 10),
        _v3(-7, 0, -10), _v3(-7, 0, 10),
        _v3(-6, 0, -10), _v3(-6, 0, 10),
        _v3(-5, 0, -10), _v3(-5, 0, 10),
        _v3(-4, 0, -10), _v3(-4, 0, 10),
        _v3(-3, 0, -10), _v3(-3, 0, 10),
        _v3(-2, 0, -10), _v3(-2, 0, 10),
        _v3(-1, 0, -10), _v3(-1, 0, 10),
        _v3(0, 0, -10), _v3(0, 0, 10),
        _v3(1, 0, -10), _v3(1, 0, 10),
        _v3(2, 0, -10), _v3(2, 0, 10),
        _v3(3, 0, -10), _v3(3, 0, 10),
        _v3(4, 0, -10), _v3(4, 0, 10),
        _v3(5, 0, -10), _v3(5, 0, 10),
        _v3(6, 0, -10), _v3(6, 0, 10),
        _v3(7, 0, -10), _v3(7, 0, 10),
        _v3(8, 0, -10), _v3(8, 0, 10),
        _v3(9, 0, -10), _v3(9, 0, 10),
    ];

    const major_line_endpoints = [
        _v3(10, 0, 10), _v3(10, 0, -10),
        _v3(-10, 0, -10), _v3(-10, 0, 10)
    ];

    const inner_grid_geo = new THREE.BufferGeometry().setFromPoints(inner_grid_line_endpoints);
    const major_line_geo = new THREE.BufferGeometry().setFromPoints(major_line_endpoints);

    const inner_grid_mat = new THREE.LineBasicMaterial({
        color: 0x303030,
        linewidth: 1,
    });

    const marjor_line_mat = new THREE.LineBasicMaterial({
        color: 0x000000,
        linewidth: 1,
    })

    const grid_obj = new THREE.Object3D();
    grid_obj.add(new THREE.LineSegments(inner_grid_geo, inner_grid_mat));
    grid_obj.add(new THREE.LineLoop(major_line_geo, marjor_line_mat));

    return grid_obj;
})();

enum SceneViewMouseKeyState {
    Left,
    Middle,
    Right,
    None
}

enum KeyState {
    Up, 
    Down,
}

const WORLD_X_AXIS = new THREE.Vector3(1, 0, 0);
const WORLD_Y_AXIS = new THREE.Vector3(0, 1, 0);
const WORLD_Z_AXIS = new THREE.Vector3(0, 0, 1);

class SceneViewCameraController {
    private __camera: THREE.PerspectiveCamera = null;
    private __sceneViewMouseKeyState: SceneViewMouseKeyState = SceneViewMouseKeyState.None;
    private __sceneViewMouseEvent: MouseEvent;

    private __keyStates: number[];

    private __press = false;
    private __lastMousePos = {x: 0, y: 0};

    __cameraLookAtTarget = new THREE.Object3D();

    mouseSpeed: number = 0.3;
    moveSpeed: number = 3;

    constructor(cam: THREE.PerspectiveCamera, keyStates: number[]) {
        this.__camera = cam;
        this.__keyStates = keyStates;
    }

    private _getKeyState(key: string): number {
        key = key.toUpperCase();
        return this.__keyStates[key.charCodeAt(0)];
    }

    updateMouseEvent(state: SceneViewMouseKeyState, event: MouseEvent) {
        this.__sceneViewMouseKeyState = state;
        this.__sceneViewMouseEvent = event;
    }

    update(deltaTime: DOMHighResTimeStamp) {
        if (!this.__sceneViewMouseEvent || !this.__sceneViewMouseKeyState)
            return;

        if (this._getKeyState('w') && this.__sceneViewMouseKeyState == SceneViewMouseKeyState.Right) {
            this.__camera.translateOnAxis(WORLD_Z_AXIS, this.moveSpeed * deltaTime);
        }

        if (this._getKeyState('a') && this.__sceneViewMouseKeyState == SceneViewMouseKeyState.Right) {
            this.__camera.translateOnAxis(WORLD_X_AXIS, this.moveSpeed * deltaTime);
        }

        if (this._getKeyState('s') && this.__sceneViewMouseKeyState == SceneViewMouseKeyState.Right) {
            this.__camera.translateOnAxis(WORLD_Z_AXIS, -this.moveSpeed * deltaTime);
        }

        if (this._getKeyState('d') && this.__sceneViewMouseKeyState == SceneViewMouseKeyState.Right) {
            this.__camera.translateOnAxis(WORLD_X_AXIS, -this.moveSpeed * deltaTime);
        }

        if (this.__sceneViewMouseKeyState != SceneViewMouseKeyState.None) {
            // calc dx, dy
            if (!this.__press) {
                this.__cameraLookAtTarget.rotation.set(this.__camera.rotation.x, 
                                                       this.__camera.rotation.y,
                                                       this.__camera.rotation.z);

                this.__cameraLookAtTarget.position.set(this.__camera.position.x, 
                                                       this.__camera.position.y, 
                                                       this.__camera.position.z);

                this.__cameraLookAtTarget.translateOnAxis(WORLD_Z_AXIS, -3);

                this.__lastMousePos = {x: this.__sceneViewMouseEvent.clientX,
                                       y: this.__sceneViewMouseEvent.clientY};
                this.__press = true;
            }

            const [lx, ly] = [this.__lastMousePos.x, this.__lastMousePos.y];
            const [cx, cy] = [this.__sceneViewMouseEvent.clientX, this.__sceneViewMouseEvent.clientY];
            const [dx, dy] = [cx - lx, cy - ly];

            // process button
            if (this.__sceneViewMouseKeyState == SceneViewMouseKeyState.Right) {

                this.__camera.rotateX(-dy * this.mouseSpeed * deltaTime);
                this.__camera.rotateOnWorldAxis(WORLD_Y_AXIS, -dx * this.mouseSpeed * deltaTime);

                this.__lastMousePos = {x: this.__sceneViewMouseEvent.clientX, y: this.__sceneViewMouseEvent.clientY};
            } else if (this.__sceneViewMouseKeyState == SceneViewMouseKeyState.Middle) {
                this.__cameraLookAtTarget.rotation.set(this.__camera.rotation.x, 
                                                       this.__camera.rotation.y,
                                                       this.__camera.rotation.z);

                this.__cameraLookAtTarget.rotateX(dy * this.mouseSpeed * deltaTime * 0.1);
                this.__cameraLookAtTarget.rotateOnWorldAxis(WORLD_Y_AXIS, dx * this.mouseSpeed * deltaTime * 0.1);
                const newCamPos = this.__cameraLookAtTarget.localToWorld(new THREE.Vector3(0, 0, 3));

                this.__camera.position.set(newCamPos.x, newCamPos.y, newCamPos.z);
                this.__camera.lookAt(this.__cameraLookAtTarget.position);
            }
        } else {
            this.__press = false;
        }
    }
}

export default class {
    _scene: Scene;
    _sceneCameras: THREE.PerspectiveCamera[];
    _sceneCameraControllers: SceneViewCameraController[];
    _sceneRenderer: THREE.WebGLRenderer | null;

    _mainSceneCamera: THREE.PerspectiveCamera;
    _mainSceneCameraController: SceneViewCameraController;

    _keyStates: number[];
    
    private __sceneViewCanvas: HTMLCanvasElement = null;
    
    private __sceneViewMouseKeyState: SceneViewMouseKeyState = SceneViewMouseKeyState.None;
    private __sceneViewMouseEvent: MouseEvent;

    constructor() {
        this._keyStates = new Array(255).fill(0);

        this._scene = new Scene();
        this._sceneCameras = [new THREE.PerspectiveCamera(60, 1.777, 0.1, 1000)];
        this._sceneCameraControllers = [new SceneViewCameraController(this._sceneCameras[0], this._keyStates)]

        this._mainSceneCamera = this._sceneCameras[0];
        this._mainSceneCameraController = this._sceneCameraControllers[0];
        
        this._sceneRenderer = null;

        this.initSceneCamera(this._sceneCameras[0]);
    }

    private initSceneRenderer() {
        this._sceneRenderer.setPixelRatio(window.devicePixelRatio);
        this._sceneRenderer.setClearColor(new THREE.Color(0.235, 0.235, 0.235), 1);
        this._sceneRenderer.setSize(this.__sceneViewCanvas.clientWidth, this.__sceneViewCanvas.clientHeight);
    }

    private initSceneCamera(cam: THREE.Camera) {
        cam.position.set(3.5, 0.5, 3.5);
        cam.lookAt(0, 0, 0);
    }

    private updateAllSceneCameraAspect() {
        for (let cam of this._sceneCameras) {
            cam.aspect = this.__sceneViewCanvas.clientWidth / this.__sceneViewCanvas.clientHeight;
        }
    }

    initSceneViewCanvas(canvas: HTMLCanvasElement | null) {
        if (canvas) {
            this._sceneRenderer = new THREE.WebGLRenderer({canvas});
            this.__sceneViewCanvas = canvas;

            this.initSceneRenderer();
            this.updateAllSceneCameraAspect();

            console.log('[Dodoco Editor]: scene view canvas init ok');
        } else {
            console.warn('[Dodoco Editor]: scene view canvas init with null canvas element.');
        }
    }

    editorSceneViewUpdate(timeStamp: DOMHighResTimeStamp) {
        let deltaTime = (__lastTimeStamp - timeStamp) / 1000;

        this._mainSceneCameraController.update(deltaTime);

        if (_SCENE_VIEW_COORD_GRID_OBJECT_3D != null) {
            this._scene.__rawScene.add(_SCENE_VIEW_COORD_GRID_OBJECT_3D);
        }
        
        this._sceneRenderer?.render(this._scene.__rawScene, this._sceneCameras[0]);
    
        if (_SCENE_VIEW_COORD_GRID_OBJECT_3D != null) {
            this._scene.__rawScene.remove(_SCENE_VIEW_COORD_GRID_OBJECT_3D);
        }

        requestAnimationFrame((timeStamp) => this.editorSceneViewUpdate(timeStamp));

        __lastTimeStamp = timeStamp;
    }

    onEditorViewportChange(width: number, height: number) {
        this.updateAllSceneCameraAspect();
        this._sceneRenderer?.setSize(this.__sceneViewCanvas.clientWidth,
                                     this.__sceneViewCanvas.clientHeight);

        console.log('editor viewport change, scene view size: ' + this.__sceneViewCanvas.clientWidth + 
                                                                'x' + this.__sceneViewCanvas.clientHeight);
    }

    onSceneViewMouseDown(e: MouseEvent) {
        switch (e.button) {
            case 0:
                this.__sceneViewMouseKeyState = SceneViewMouseKeyState.Left;
                break;
            case 1:
                this.__sceneViewMouseKeyState = SceneViewMouseKeyState.Middle;
                break;
            case 2:
                this.__sceneViewCanvas.style.cursor = 'grab';
                this.__sceneViewMouseKeyState = SceneViewMouseKeyState.Right;
                break;
        }

        this._mainSceneCameraController.updateMouseEvent(this.__sceneViewMouseKeyState,
                                                         this.__sceneViewMouseEvent);        
    }

    onSceneViewMouseUp(e: MouseEvent) {
        this.__sceneViewMouseKeyState = SceneViewMouseKeyState.None;
        this.__sceneViewMouseEvent = e;

        this.__sceneViewCanvas.style.cursor = 'default';
        
        this._mainSceneCameraController.updateMouseEvent(this.__sceneViewMouseKeyState,
            this.__sceneViewMouseEvent);
    }

    onSceneViewMouseMove(e: MouseEvent) {
        this.__sceneViewMouseEvent = e;
        
        this._mainSceneCameraController.updateMouseEvent(this.__sceneViewMouseKeyState,
            this.__sceneViewMouseEvent);
    }

    onKeyboardEvent(e: KeyboardEvent, state: number) {
        let ascii = e.keyCode;
        if (ascii >= 0 && ascii <= 255) {
            this._keyStates[ascii] = state;
        }
    }
};
